<?php

/**
 * ownCloud - user_joomla
 *
 * @author pipapai.com
 * @copyright 2013 pipapai.com
 *
 */

require_once 'phpass/PasswordHash.php';

/**
 * Class for user management in a Joomla MySQL Database
 * Note: we only allow to create user for sub-account, the company account is managed in jos_jobs_employer account 
 *       the employer account creation is handled by app_company
 */
class OC_USER_JOOMLA implements \OCP\UserInterface {
   /**
    * @var PasswordHash
    */
   static private $hasher=null;

   private function getHasher() {
      if(!self::$hasher) {
         //we don't want to use DES based crypt(), since it doesn't return a has with a recognisable prefix
         $forcePortable=(CRYPT_BLOWFISH!=1);
         self::$hasher=new PasswordHash(8, $forcePortable);
      }
      return self::$hasher;

   }

   /**
    * @brief Create a new user
    * @param $email The email of the user to create
    * @param $password The password of the new user
    * @returns true/false
    *
    * Creates a new user. Basic checking of username is done in OC_User
    * itself, not in its subclasses.
    */
   public function createUser( $email, $password ) {
      if( !$this->userExists($email) ) {
         $hasher=$this->getHasher();
         $hash = $hasher->HashPassword($password.OC_Config::getValue('passwordsalt', ''));
         $db = PFactory::getDbo(); 
         $query = 'INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ' . 
                  $db->quote($email) . ', ' . 
                  $db->quote($hash) . ')'; 
         $db->setQuery(PUtil::replaceOCPrefix($query));
         $result = $db->execute(); 
      }
      else {
         $result = true; 
      }

      return $result ? true : false;
   }

   /**
    * @brief delete a user
    * @param $email The username of the user to delete
    * @returns true/false
    *
    * Deletes a user
    */
   public function deleteUser( $email ) {
      if($this->userExistsInCloud($email) ) {
         // Delete user-group-relation
         $db = PFactory::getDbo();
         $query = 'DELETE FROM `*PREFIX*users` WHERE uid = ' . $db->quote($email); 
         $db->setQuery(PUtil::replaceOCPrefix($query));
         $db->execute(); 
      }
      
      return true;
   }

   /**
    * @brief Set password
    * @param $email The username
    * @param $password The new password
    * @returns true/false
    *
    * Change the password of a user
    */
   public function setPassword( $email, $password ) {
      if( $this->userExistsInCloud($email) ) {
         $hasher=$this->getHasher();
         $hash = $hasher->HashPassword($password.OC_Config::getValue('passwordsalt', ''));
         $db = PFactory::getDbo();
         $query = 'UPDATE `*PREFIX*users` SET `password` = ' . $db->quote($hash) . ' WHERE `uid` = ' . $db->quote($email); 
         $db->setQuery(PUtil::replaceOCPrefix($query));
         $db->execute();

         return true;
      }else{
         return false;
      }
   }
   
   /**
    * @brief Set display name
    * @param $email The username
    * @param $displayName The new display name
    * @returns true/false
    *
    * Change the display name of a user
    */
   public function setDisplayName( $email, $displayName ) {
      if( $this->userExistsInCloud($email) ) {
         $db = PFactory::getDbo();
         $query = 'UPDATE `*PREFIX*users` SET `displayname` = ' . $db->quote($displayName) . ' WHERE `uid` = ' . $db->quote($email); 
         $db->setQuery(PUtil::replaceOCPrefix($query));
         $db->execute();
         return true;
      }else{
         return false;
      }

      return true; 
   }

   /**
    * @brief get display name of the user
    * @param $uid user ID of the user
    * @return display name
    */
   public function getDisplayName($uid) {
      /* get it from normal sub account */
      if( $this->userExistsInCloud($uid) ) {
         $db = PFactory::getDbo();
         $query = 'SELECT displayname FROM `*PREFIX*users` WHERE `uid` = ' . $db->quote($uid); 
         $db->setQuery(PUtil::replaceOCPrefix($query));
         $result = $db->loadResult(); 
         $result = $result ? trim($result) : $uid; 
         return empty($result) ? $uid : $result; 
      }
      else {
         /* let's see it's from company account */
         $db = JFactory::getDbo();
         $query = "SELECT e.comp_name " .
               " FROM #__jobs_employer AS e " . 
               " LEFT JOIN #__users AS u ON u.id=e.user_id " . 
               " WHERE u.block=0 AND u.id=" . $db->quote($uid); 
         $db->setQuery($query);
         $comp_name = $db->loadResult();
         return !empty($comp_name) ? $comp_name : ""; 
      }
   }
   
   /**
    * @brief Get a list of all display names
    * @returns array with  all displayNames (value) and the correspondig emails (key)
    *
    * Get a list of all display names and user ids.
    */
   public function getDisplayNames($search = '', $limit = null, $offset = null) {
      /* only get display names belong to same master */
      if(OC_User::getUser()) {
         $master = $this->getUserMasterAccount(OC_User::getUser());
         $users = $this->getUsersFromMaster($master);
         $db = PFactory::getDbo();
         $query = 'SELECT DISTINCT `uid`, IF(`displayname` AND length(trim(`displayname`))>0, `displayname`, `uid`) AS displayname ' . 
                  ' FROM `*PREFIX*users` WHERE ' . 
                  ' `displayname` LIKE ' . $db->quote($search.'%') . 'OR ' . 
                  ' `uid` LIKE ' . $db->quote($search.'%'); 
         $db->setQuery(PUtil::replaceOCPrefix($query), $offset, $limit);
         return $query->loadAssocList('uid', 'displayname');
      }

      return array();
   }
   
   /**
    * @brief Check if the password is correct
    * @param $email The username
    * @param $password The password
    * @returns string
    *
    * Check if the password is correct without logging in the user
    * returns the user id or false
    */
   public function checkPassword( $email, $password) {
      // Check $email here is mobile number ?
      if (JobHelper::isPhone($email) && isFakeEmail($email)) {
         $userid = $this->checkPasswordByMobile($email,$password);
         return $userid ? $userid : false;
      }

      // Get a database object
      $db = JFactory::getDbo();
      $query = 'SELECT u.uid, u.password,u.displayname,j.email AS companyemail, e.owner' .
               ' FROM *PREFIX*users AS u' .
               ' LEFT JOIN *PREFIX*users_extended AS e ON e.uid=u.uid ' .
               ' LEFT JOIN #__users AS j ON j.id = e.owner ' .
               ' WHERE u.uid = ' . $db->quote($email);

      $db->setQuery(PUtil::replaceOCPrefix($query));
      $row=$db->loadObject();
      if($row) {
         $storedHash=$row->password;
         if ($storedHash[0]=='$') {//the new phpass based hashing
            $hasher=$this->getHasher();
            if($hasher->CheckPassword($password.OC_Config::getValue('passwordsalt', ''), $storedHash)) {
               /* simulate a juser session */
               $user = new JUser; 
               $user->set('id', 0);
               $user->set('username', $row->uid);
               $user->set('name', empty($row->displayname) ? $row->uid : $row->displayname);
               $user->set('owner', $row->owner);
               $user->set('demouser', isDemoUser($row->companyemail));
               $user->set('testuser',isTestUser($row->companyemail));
               $app = PFactory::getApplication();
               $app->setUserSession($user);

               return $row->uid;
            }else{
               $userid = $this->checkPasswordByMobile($email,$password);
               return $userid ? $userid : false;
            }
         }else{//old sha1 based hashing
            if(sha1($password)==$storedHash) {
               //upgrade to new hashing
               $this->setPassword($row->uid, $password);
               return $row->uid; 
            }else{
               $userid = $this->checkPasswordByMobile($email,$password);
               return $userid ? $userid : false;
            }
         }
      }
      else {
         // otherwise to login, the user must be company user 
         $query = 'SELECT u.`username`, u.`id`, u.`password`, c.`cb_usertype`, c.`avatar`'
            . ' FROM `#__users` as u LEFT JOIN `#__comprofiler` as c ON u.id=c.user_id '
            . ' WHERE u.block=0 AND u.email =' . $db->quote($email)
            ;

         $db->setQuery( $query );
         $result = $db->loadObject();
         if($result)
         {
            $parts   = explode( ':', $result->password );
            $crypt   = $parts[0];
            $salt = @$parts[1];
            $testcrypt = JUserHelper::getCryptedPassword($password, $salt);
            if ( $crypt == $testcrypt ) {
               $user = JUser::getInstance($result->id);
               $user->setLastVisit();
               $user->cb_usertype = $result->cb_usertype;
               $user->avatar = $result->avatar;
               $user->demouser = isDemoUser($user->email);
               $user->testuser = isTestUser($user->email);
               $app = PFactory::getApplication();
               $app->setUserSession($user);

               return $result->id;
            }
         }

         // Check $email here is mobile number ?
         if (JobHelper::isPhone($email)) {
            $userid = $this->checkPasswordByMobile($email,$password);
         } else {
            $userid = $this->checkAccessToken($email,$password);
         }
         return $userid ? $userid : false;
      }

   }

   /**
    * @brief Check if the password is correct with mobile account
    * @param $mobile The mobile
    * @param $password The password
    * @returns string
    *
    * Check if the password is correct without logging in the user
    * returns the user id or false
    */
   public function checkPasswordByMobile( $mobile, $password) {
      // Get a database object
      $db = JFactory::getDbo();
      // otherwise to login, the user must be company user 
      $query = 'SELECT u.`username`, u.`id`, u.`password`, c.`cb_usertype`, c.`avatar`'
         . ' FROM `#__users` as u LEFT JOIN `#__comprofiler` as c ON u.id=c.user_id '
         . ' LEFT JOIN `#__users_mobile` as m ON u.id=m.userid '
         . ' WHERE u.`block`=0 AND m.`primary` = 1 AND m.`mobile` =' . $db->quote($mobile)
         ;

      $db->setQuery( $query );
      $result = $db->loadObject();
      if($result)
      {
         $parts   = explode( ':', $result->password );
         $crypt   = $parts[0];
         $salt = @$parts[1];
         $testcrypt = JUserHelper::getCryptedPassword($password, $salt);
         if ( $crypt == $testcrypt ) {
            $user = JUser::getInstance($result->id);
            $user->setLastVisit();
            $user->cb_usertype = $result->cb_usertype;
            $user->avatar = $result->avatar;
            $user->demouser = isDemoUser($user->email);
            $user->testuser = isTestUser($user->email);
            $app = PFactory::getApplication();
            $app->setUserSession($user);

            return $result->id;
         }
      }

      $userid = $this->checkAccessToken($mobile,$password);
      return $userid ? $userid : false;
   }

   /**
    * Check Access Token
    * @param  string $username Could be user email or mobile
    * @param  string $password In this case the password here mostly it's our access_token
    * @return boolean
    */
   public function checkAccessToken( $username, $password) {
      $db = PFactory::getDbo();
      // Genarate Token by given username
      $query = 'SELECT u.`username`, u.`id`, u.`lastvisitDate`, c.`cb_usertype`, c.`avatar`, u.`password` '
            . ' FROM `#__users` as u LEFT JOIN `#__comprofiler` as c ON u.id=c.user_id '
            . ' WHERE u.block=0 AND u.email =' . $db->quote($username);
      $db->setQuery($query);
      $result = $db->loadObject();
      if (empty($result)) {
         // Try mobile way
         $query = 'SELECT u.`username`, u.`id`, u.`lastvisitDate`, c.`cb_usertype`, c.`avatar`, u.`password` '
            . ' FROM `#__users` as u LEFT JOIN `#__comprofiler` as c ON u.id=c.user_id '
            . ' LEFT JOIN `#__users_mobile` as m ON u.id=m.userid '
            . ' WHERE u.block=0 AND m.primary = 1 AND m.mobile =' . $db->quote($username);
         $db->setQuery($query);
         $result = $db->loadObject();
      }

      if (empty($result))
         return false;

      $parts   = explode(':', $result->password);
      $salt = empty($parts[1]) ? "" : $parts[1];
      $gen_token = shortHash($result->username.$salt.$result->lastvisitDate, 20);

      if ($gen_token == trim($password)) {
         $query = "SELECT time FROM #__users_mobile_token WHERE token = ".$db->quote($gen_token);
         $db->setQuery($query);
         $time = $db->loadResult();

         $lifetime = OC_Config::getValue('mobiletokenlifttime', 0);
         // Token expired or does not exist
         if ( (time()-intval($time)) > $lifetime )
            return false;

         // Match! Simulate a juser session now
         $user = JUser::getInstance($result->id);
         // $user->setLastVisit();
         $user->cb_usertype = $result->cb_usertype;
         $user->avatar = $result->avatar;
         $user->demouser = isDemoUser($user->email);
         $user->testuser = isTestUser($user->email);
         $app = PFactory::getApplication();
         $app->setUserSession($user);

         return $result->id;
      }

      return false;
   }

   /**
    * @brief Get a list of all users
    * @returns array with all emails
    *
    * Get a list of all users.
    */
   public function getUsers($search = '', $limit = null, $offset = null) {
      $db = PFactory::getDbo();
      $query = 'SELECT DISTINCT `uid`, `displayname` FROM `*PREFIX*users` WHERE `displayname` LIKE ' . $db->quote($search.'%'); 
      $db->setQuery(PUtil::replaceOCPrefix($query), $offset, $limit);
      return $db->loadAssocList(null, 'displayname');
   }

   /**
    * @brief check if a user exists
    * @param string $uid the username 
    * @return boolean
    */
   public function userExists($uid) {
      if($this->userExistsInCloud($uid))
         return true; 

      /* now let's check if it's master user and exists company table */
      /* unfortunatley we cannot use Joomla getUser as it has to go through Joomla login process to activate session */
      return $this->userExistsInJoomla($uid); 
   }


  /**
   * @brief get the user's home directory
   * @param string $uid the user id
   * @return boolean
   */
   public function getHome($uid) {
      $master = $uid; 
      if($this->userExistsInCloud($uid)) {
         $master = $this->getUserMasterAccount($uid); 
         if(!$master) {
            return false; 
         }
         else {
            /* going to be in same master quota and data path */
            /* fall through */
         }
      }
      else if(!$this->userExistsInJoomla($uid)) {
         return false; 
      }

      $datadir = getUserDataDir($master);
      $extdir = getUserExtDir($master); 
      return PFactory::isJobseekerUser($master) ? 
             OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ) . $datadir . '/jobseeker/' . $extdir : 
             OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ) . $datadir . '/company/' . $extdir; 

   }

  /**
   * @brief Check if backend implements actions
   * @param $actions bitwise-or'ed actions
   * @returns boolean
   *
   * Returns the supported actions as int to be
   * compared with OC_USER_BACKEND_CREATE_USER etc.
   */
   public function implementsActions($actions) {
      return (bool)((OC_USER_BACKEND_CREATE_USER | OC_USER_BACKEND_SET_DISPLAYNAME | 
                     OC_USER_BACKEND_GET_DISPLAYNAME | OC_USER_BACKEND_SET_PASSWORD | 
                     OC_USER_BACKEND_CHECK_PASSWORD | OC_USER_BACKEND_GET_HOME) & $actions);
   }


   /**
    * @return bool
    */
   public function hasUserListings() {
      return true;
   }

   /** extended functions */

   /**
    * @brief check if a user exists in Joomla DB 
    * @param string $uid the user id 
    * @return boolean
    */

   public function userExistsInCloud($uid) {
      $db = PFactory::getDbo();
      $query = 'SELECT COUNT(*) AS num FROM `*PREFIX*users` WHERE `uid` = ' . $db->quote($uid); 
      $db->setQuery(PUtil::replaceOCPrefix($query));
      $num = $db->loadResult(); 
      if($num && $num > 0)
         return true;
      
      return false; 
   }

   /**
    * @brief check if a user exists in Joomla DB 
    * @param string $uid the user id 
    * @return boolean
    */
   public function userExistsInJoomla($uid) {
     /* now let's check if it's master user and exists company table */
      /* unfortunatley we cannot use Joomla getUser as it has to go through Joomla login process to activate session */
      $db = JFactory::getDbo();
      $query = "SELECT COUNT(*) AS num " .
               " FROM #__comprofiler AS e " .
               " LEFT JOIN #__users AS u ON u.id=e.user_id " .
               " WHERE u.block=0 AND " . (is_numeric($uid) ? "u.id=" . intval($uid) : "u.email=" . $db->quote($uid));
      $db->setQuery($query);
      if($db->loadResult() > 0)
         return true;

      return false; 
   }

   /**
    * @briref check if a user belong to one master account - which has to be employer account 
    */
   public function getUserMasterAccount($email) {
      $db = PFactory::getDbo(); 
      $query = 'SELECT DISTINCT( `owner` ) FROM `*PREFIX*users_extended` WHERE `uid` = ' . $db->quote($email); 
      $db->setQuery(PUtil::replaceOCPrefix($query));
      $result = $db->loadObject(); 
      if($result)
         return $result->owner; 

      return false; 
   }

   /**
    * @briref get all users belong to same master 
    */
   public function getUsersFromMaster($email) {
      // No need for more comments
      $users = array();
      $master = JobHelper::getUserByEmail($email); 
      if(!$master)
         return $users; 

      $db = PFactory::getDbo(); 
      $query = 'SELECT DISTINCT( `uid` ) FROM `*PREFIX*users_extended` WHERE `owner` = ' . $db->quote($email); 
      $db->setQuery(PUtil::replaceOCPrefix($query));
      return $db->loadAssocList(null, 'uid'); 
   }
      
}
